﻿using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Synchronization.Interface;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Sustainalytics.Entities.ConnectionStrings;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Entities.ProductInvolvement;

namespace Sustainalytics.ClientDW.Synchronization
{

    public class CorporateDataCleaner
    {
        private readonly IMongoCollection<CorporateData> _corporateDataCollection;
        private readonly IMongoCollection<GCOverallCompanySummary> _gcOverallSummaryCollection;
        private readonly IMongoCollection<GCOverallCompanySummaryExtended> _gcOverallSummaryExtendedCollection;
        private readonly IMongoCollection<GCPrincipleEventIndicatorExtended> _gcPrincipleEventIndicatorExtendedCollection;
        private readonly IMongoCollection<GCPrincipleSummary> _gcPrincipleSummaryCollection;
        private readonly IMongoCollection<GCPrincipleSummaryExtended> _gcPrincipleSummaryExtendedCollection;

        private readonly IMongoCollection<ProductInvolvementData> _piCompanyAssessmentCollection;
        private readonly SyncContext _syncContext;
        private readonly bool _syncDsoData;

        public CorporateDataCleaner(SyncContext syncContext)
        {
            MongoFactory.GetCollectionFromDatabase<CorporateData>(syncContext.GAConnectionStrings[ConnectionStringType.ClientDW.ToString()]);

            _syncContext = syncContext;
            _corporateDataCollection = MongoFactory.GetCollectionFromDatabase<CorporateData>(syncContext.GAConnectionStrings[ConnectionStringType.ClientDW.ToString()]);

            _gcOverallSummaryCollection = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummary>(syncContext.GAConnectionStrings[ConnectionStringType.ClientDW.ToString()]);
            _gcOverallSummaryExtendedCollection = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummaryExtended>(syncContext.GAConnectionStrings[ConnectionStringType.ClientDW.ToString()]);
            _gcPrincipleEventIndicatorExtendedCollection = MongoFactory.GetCollectionFromDatabase<GCPrincipleEventIndicatorExtended>(syncContext.GAConnectionStrings[ConnectionStringType.ClientDW.ToString()]);
            _gcPrincipleSummaryCollection = MongoFactory.GetCollectionFromDatabase<GCPrincipleSummary>(syncContext.GAConnectionStrings[ConnectionStringType.ClientDW.ToString()]);
            _gcPrincipleSummaryExtendedCollection = MongoFactory.GetCollectionFromDatabase<GCPrincipleSummaryExtended>(syncContext.GAConnectionStrings[ConnectionStringType.ClientDW.ToString()]);

            _piCompanyAssessmentCollection = MongoFactory.GetCollectionFromDatabase<ProductInvolvementData>(syncContext.GAConnectionStrings[ConnectionStringType.ProductInvolvementDW.ToString()]);

            _syncDsoData = bool.Parse(ConfigurationManager.AppSettings["SyncDsoDatabase"]);

        }

        public async Task SetResearchNotAvailable()
        {
            _syncContext.Log("Start setting research flag NotAvailable for DS only products");

            await UpdateComprehensiveEsgResearchState();

            await UpdateCoreEsgResearchState();

            await UpdateRiskRatingResearchState();
            
            await UpdateControversiesResearchState();
            
            await UpdateProductInvolvementResearchState();

            await UpdateSustainableProductsResearchState();

            await UpdateGlobalCompactResearchState();
            
            await UpdateCorporateGoveranceResearchState();
        }

       
        private async Task UpdateComprehensiveEsgResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                            .Find(x => x.ResearchParentId == null &&
                                                       x.ComprehensiveEsgResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                       !x.ComprehensiveEsgResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                            .Project(x => new
                                            {
                                                x.Id,
                                                x.CapitalIqId
                                            })
                                            .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x=> x.Id)),
                                                    Builders<CorporateData>.Update.Set(cd => cd.ComprehensiveEsgResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for ComprehensiveEsgResearchState");
                });
            }
        }

        private async Task UpdateCoreEsgResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                            .Find(x => x.ResearchParentId == null &&
                                                       x.CoreEsgResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                       !x.CoreEsgResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                            .Project(x => new
                                            {
                                                x.Id,
                                                x.CapitalIqId
                                            })
                                            .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x => x.Id)),
                                            Builders<CorporateData>.Update.Set(cd => cd.CoreEsgResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for CoreEsgResearchState");
                });
            }
        }

        private async Task UpdateRiskRatingResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                            .Find(x => x.ResearchParentId == null &&
                                                       x.RiskRatingResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                       !x.RiskRatingResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                            .Project(x => new
                                            {
                                                x.Id,
                                                x.CapitalIqId
                                            })
                                            .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x => x.Id)),
                                            Builders<CorporateData>.Update.Set(cd => cd.RiskRatingResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for RiskRatingEsgResearchState");
                });
            }
        }

        private async Task UpdateControversiesResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                            .Find(x => x.ResearchParentId == null &&
                                                       x.ControversyResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                      !x.ControversyResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                            .Project(x => new
                                            {
                                                x.Id,
                                                x.CapitalIqId
                                            })
                                            .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x => x.Id)),
                                                Builders<CorporateData>.Update.Set(cd => cd.ControversyResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for ControversyResearchState");
                });
            }
        }

        private async Task UpdateProductInvolvementResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                            .Find(x => x.ResearchParentId == null &&
                                                       x.ProductInvolvementResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                      !x.ProductInvolvementResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                            .Project(x => new
                                            {
                                                x.Id,
                                                x.CapitalIqId
                                            })
                                            .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x => x.Id)),
                                                      Builders<CorporateData>.Update.Set(cd => cd.ProductInvolvementResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for ProductInvolvementResearchState");
                });
            }
        }

        private async Task UpdateSustainableProductsResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                           .Find(x => x.ResearchParentId == null &&
                                                      x.SolutionsResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                     !x.SolutionsResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                           .Project(x => new
                                           {
                                               x.Id,
                                               x.CapitalIqId
                                           })
                                           .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x => x.Id)),
                                                      Builders<CorporateData>.Update.Set(cd => cd.SolutionsResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for SolutionsResearchState");
                });
            }
        }


        private async Task UpdateGlobalCompactResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                            .Find(x => x.ResearchParentId == null &&
                                                       x.GccsResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                      !x.GccsResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                            .Project(x => new
                                            {
                                                x.Id,
                                                x.CapitalIqId
                                            })
                                            .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x => x.Id)),
                                                                        Builders<CorporateData>.Update.Set(cd => cd.GccsResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for GlobalCompactResearchState");
                });
            }
        }

        private async Task UpdateCorporateGoveranceResearchState()
        {
            var companiesToUpdate = await _corporateDataCollection
                                            .Find(x => x.ResearchParentId == null &&
                                                       x.CorporateGovernanceResearchState.ResearchState != ResearchState.ResearchNotAvailable &&
                                                      !x.CorporateGovernanceResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                                            .Project(x => new
                                            {
                                                x.Id,
                                                x.CapitalIqId
                                            })
                                            .ToListAsync();

            if (companiesToUpdate.Any())
            {
                _corporateDataCollection.UpdateMany(Builders<CorporateData>.Filter.In(cd => cd.Id, companiesToUpdate.Select(x => x.Id)),
                                            Builders<CorporateData>.Update.Set(cd => cd.CorporateGovernanceResearchState.ResearchState, ResearchState.ResearchNotAvailable));

                companiesToUpdate.ForEach(comp =>
                {
                    _syncContext.Log("Updated company with id: " + comp.CapitalIqId + " research flag NotAvailable for CorporateGovernanceResearchState");
                });
            }
        }

        public void DeleteCompaniesWithNoResearchData()
        {
            _syncContext.Log("Checking for companies with no research states for specific product");
            var gaCorporateData = GetCorporateDataForClientFacingProduct(ClientFacingProduct.GA);
            var dsCorporateData = GetCorporateDataForClientFacingProduct(ClientFacingProduct.DS);
            
            //Remove all companies that are ResearchEntity and:
            //Have all researches not available or company is not GA on any product
            var gaCoverageToBeDeleted = GetCoverageCompaniesToBeDeleted(gaCorporateData.Select(a => a.Id).ToList());
            gaCorporateData.AddRange(gaCoverageToBeDeleted);

            var dsCoverageToBeDeleted = GetCoverageCompaniesToBeDeleted(dsCorporateData.Select(a => a.Id).ToList());
            dsCorporateData.AddRange(dsCoverageToBeDeleted);
            var taskListCompanySearch = new List<Task>();

            if (gaCorporateData.Any())
            {
                _syncContext.Log("The following companies will be deleted for GA");
                CompaniesToDeleteLogging(gaCorporateData);

                var dataToDeleteForGA = gaCorporateData.Select(a => a.Id).ToList();
                _syncContext.Log("Deleting from GA CorporateData");
                _corporateDataCollection.DeleteMany(x => dataToDeleteForGA.Contains(x.Id));
            }

            if (_syncDsoData && dsCorporateData.Any())
            {
                _syncContext.Log("The following companies will be deleted for DS");
                CompaniesToDeleteLogging(dsCorporateData);

                _syncContext.Log("Deleting from DS CompanySearch");

            }
            Task.WaitAll(taskListCompanySearch.ToArray());
        }

        private List<CorporateData> GetCorporateDataForClientFacingProduct(ClientFacingProduct clientFacingProduct)
        {
            return _corporateDataCollection.Find(a => a.ResearchParentId == null &&
                ((a.ComprehensiveEsgResearchState.ResearchState == ResearchState.ResearchNotAvailable &&
                a.CoreEsgResearchState.ResearchState == ResearchState.ResearchNotAvailable &&
                a.RiskRatingResearchState.ResearchState == ResearchState.ResearchNotAvailable &&
                a.ControversyResearchState.ResearchState == ResearchState.ResearchNotAvailable &&
                a.ProductInvolvementResearchState.ResearchState == ResearchState.ResearchNotAvailable &&
                a.SolutionsResearchState.ResearchState == ResearchState.ResearchNotAvailable &&
                a.CorporateGovernanceResearchState.ResearchState == ResearchState.ResearchNotAvailable &&
                a.GccsResearchState.ResearchState == ResearchState.ResearchNotAvailable) ||
                (!a.ComprehensiveEsgResearchState.ClientFacingProducts.Contains(clientFacingProduct) &&
                 !a.CoreEsgResearchState.ClientFacingProducts.Contains(clientFacingProduct) &&
                 !a.RiskRatingResearchState.ClientFacingProducts.Contains(clientFacingProduct) &&
                 !a.ControversyResearchState.ClientFacingProducts.Contains(clientFacingProduct) &&
                 !a.ProductInvolvementResearchState.ClientFacingProducts.Contains(clientFacingProduct) &&
                 !a.SolutionsResearchState.ClientFacingProducts.Contains(clientFacingProduct) &&
                 !a.CorporateGovernanceResearchState.ClientFacingProducts.Contains(clientFacingProduct) &&
                 !a.GccsResearchState.ClientFacingProducts.Contains(clientFacingProduct)))).ToList();
        }

        private void CompaniesToDeleteLogging(List<CorporateData> corporateData)
        {
            corporateData.ForEach(cd =>
            {
                _syncContext.Log(
                    $"CompanyId: {cd.Id}, CapitalIQIID: {cd.CapitalIqId}, CompanyName: {cd.CompanyName}, " + 
                    $"ResearchParentId: {(cd.ResearchParentId.HasValue ? cd.ResearchParentId.Value.ToString() : "N\\A")}");
            });
        }

        private List<CorporateData> GetCoverageCompaniesToBeDeleted(List<Guid> ids)
        {
            if (!ids.Any()) return new List<CorporateData>();
            var coverages = _corporateDataCollection.Find(a => a.ResearchParentId != null).ToList().ToLookup(a => a.ResearchParentId);
            var coverageToBeDelete = new List<CorporateData>();
            foreach (var coverage in coverages)
            {
                if (!ids.Contains(coverage.Key.GetValueOrDefault())) continue;
                coverageToBeDelete.AddRange(coverage);
            }
            return coverageToBeDelete;
        }

        public class GetMongoCollection<T>
        {
            public IMongoCollection<T> MongoCollection { get; private set; }
            public string CollectionName { get; private set; }

            public GetMongoCollection(string connectionString)
            {
                CollectionName = typeof(T).Name;
                var mongoClient = new MongoClient(connectionString);
                var database = mongoClient.GetDatabase(connectionString.Split('/').Last().Trim());

                MongoCollection = database.GetCollection<T>(CollectionName);
            }
        }
    }
}
